今天來看傳值與傳址
call by value
傳值與call by reference
傳址指的是電腦記憶體中的東西,與程式的參照傳遞互動的模式。
call by value
當我們創造變數並給值時,變數會指向值在電腦記憶體中的位置,若我們以這個值為參照,指定另一個變數指向這個值時,電腦會在記憶體中新增(複製)一個新值,讓後來的這個變數指向新的值。
在JavaScript裡,布林值、字串、數值、null、undefined都是call by value。
來看看以下程式碼
var a = 100;
var b;
b = a;
a = a - 70;
console.log('a現在是' + a);
console.log('b現在是' + b);
現在變數a是30,變數b是100。
挺合理的,a和b指向的值在電腦記憶體裡不一樣,當a修改時,b並不會被影響,這種特性就是call by value。
call by reference
當我們創造變數並給值(物件)時,變數會指向物件在電腦記憶體中的位置,若我們以這個物件為參照,指定另一個變數指向這物件,這個變數就會指向電腦記憶體中同樣的物件,不會有新的物件在記憶體中被創造出來。
在JavaScript裡,物件、陣列、函式都是call by reference。
來看看以下程式碼
var c = { hello : '安安' };
var d;
d = c;
c.hello = '你好';
console.log(c);
console.log(d);
如果用上面a、b的例子(call by value)來看c、d,那麼:console.log(c)
應該是顯示出{ hello : '你好' }
console.log(d)
應該是顯示出{ hello : '安安' }
畢竟d和c指向的記憶體物件應該不一樣,彼此會互不影響,來看看結果
疑?兩個都一樣?
當我們給變數一個物件,其實賦予變數指向這個物件在電腦記憶體的位址,而d = c
,變數d也指向同一個物件,同一個該物件在記憶體的位址。
當c.hello = '你好'
時,c指向的物件,hello屬性變成'你好',因為d指向的物件和c是同一個,d自然也是{ hello : '你好' }
囉。
承接程式碼,若用函式傳參數(物件),結果也是一樣的
function changeHello(obj){
obj.hello = '天氣真好';
}
changeHello(d);
console.log(c.hello);
console.log(d.hello);
上面說過,物件是by reference
,c、d都是指向記憶體中的同一個物件,結果自然是:
兩個都一樣
另一個例子:
var e = { hello : '安安' };
var f = { hello : '安安' };
e.hello = '哎呀';
console.log(e);
console.log(f);
疑?按照上面的說明,此時e和f應該都是{ hello : '哎呀' }
才對啊?
這是因為,雖然他們的值字面上看一樣,但在電腦記憶體位置中,這兩個物件在記憶體中是獨立分開的,=
運算子會建立一個新的命名空間,而且用到了物件實體語法來創造物件,是故e和f指向各自指向不同的記憶體位置,彼此自然不影響囉。
這邊再給大家看看call by value和call by reference的差別
call by value
var byValue1 = 100;
var byValue2 = byValue1;
var byValue3 = byValue1;
var byValue4 = byValue1;
console.log(byValue1,byValue2,byValue3,byValue4);
都是100
byValue1 = byValue1 - 30;
console.log(byValue1,byValue2,byValue3,byValue4);
只有byValue1的值受到了影響,這代表byValue1、byValue2、byValue3、byValue4指向的數值都是電腦記憶體中獨立分開的值,所以彼此互不影響。
call by reference
var obj1 = { ByReference : 100 };
var obj2 = obj1;
var obj3 = obj1;
var obj4 = obj1;
console.log(obj1,obj2,obj3,obj4);
現在電腦記憶體中的{ByReference:100}其實只有一個,只是同時4個變數指向它。
obj3.ByReference = obj3.ByReference - 30;
console.log(obj1,obj2,obj3,obj4);
最後,來加碼分享面試時有被考到的題目,當時已經知道by value
與by reference
的概念,但還是被陷阱坑到。
var a = { n : 1 };
var b = a;
a.x = a = { n : 2 };
console.log(a.x)
console.log(b.x)
求a.x和b.x,console分別顯示出來的內容
解析
可以分三部分來看
第一部分
{ n : 1 }
var b = a;
,變數b也指向變數a指向的{ n : 1 }
因為by reference的關係,a、b此時都指向憶體中的同一個
{ n : 1 }
第二部分
a.x = a = { n : 2 }
=
運算子是右相依性,所以這行乍看是先從右邊看到左邊....嗎?
別忘記決定運算子順序的是優先性與相依性,可以參考MDN的運算子優先性表格
.
運算子的優先性高於=
運算子
所以先看最左邊的a.x
,但因為a物件{ n : 1 }
並沒有x屬性的存在,
於是就創造x這個屬性,a.x
值是undefined
。
a、b此時共同指向的物件變成
{ n : 1 , x : undefined }
然後才因為=
運算子,由右看到左。
a = { n : 2 }
a改指向記憶體中的新物件
{n:2}
,因為by reference的關係,此時b仍指向記憶體中的物件{ n : 1 , x : undefined }
。
a.x = a
將a現在指向的物件{ n : 2 }
賦予給a.x
,這邊的a.x
其實是先前與b一同指向的{ n : 1 , x : undefined }
的x屬性,也就是undefined
,
此時{ n : 1 , x : undefined }
這個物件變成{ n : 1 , x : { n : 2 } }
。
現在的狀況
a指向{ n : 2 }
b指向{ n : 1 , x : { n : 2 } }
第三部分
console.log(a.x)
這個時候的a指向{n : 2 }
,console.log卻是要看a.x
,{ n : 2 }
並沒有屬性x,於是創造x屬性,a物件變成是{n : 2 , x : undefined }
,a.x
就是undefined
囉!
console.log(b.x)
這個時候的b指向{ n : 1 , x : { n : 2 } }
,b.x
自然是{ n : 2 }
瀏覽器console,看看結果:
小結
JS同時具有call by value
和call by reference
的特性,這種傳遞特性稱作call by sharing
。理解這種特性是很重要的,畢竟面試會考它是物件導向語言啊,瞭解JS物件、函式的call by reference,可以幫助我們避掉一些開發上的bug。
今天的筆記內容可以參照Udemy課程:JavaScript 全攻略:克服JS 的奇怪部分4-36